if

shell 中的 if 语句的作用与其他语言的 if 语句相同: 条件检测.

exit(0) 代表命令运行成功

else 部分就像 then 部分一样, 可以包含任意数量的命令, 包括其他的 if .. then 语句.

if 语句还有另一个特征. 如果 if 后的条件是系列命令, 那么最后一个命令 的 exit 值被用作这个语句块的条件值, 并由此来决定条件是否成立.

if 是如何工作的

  1. shell 运行 if 之后的命令
  2. shell 检查命令的 exit 状态
  3. exit 的状态为 0 意味着成功, 非 0 意味着失败.
  4. 如果成功, shell 执行 then 部分的代码.
  5. 如果失败, shell 执行 else 部分的代码
  6. 关键字 fi 标识 if 块的结束

在 smsh 中增加 if

为了处理 if..then..else..fi 控制流程, 需要在原有的 smsh 增加一层 process, process 通过寻找关键字, 比如 if,then 和 fi, 来管理脚本流程, 在适当的时候调用 fork 和 exec. process 必须记录条件命令的结果以便能够 处理 then 和 else 块.

process 将脚本看做一个接一个的代码区域. 第一个区域就是 then 代码块, 第 2 个区域是 else 代码块, 第三个是在 if 语句之外的代码块.

考虑 if 语句之外的区域, 这里称之为中立区(neutral). 对于这类区域代码, 简单的读一条, 分析一条, 执行一条.

接下载是在 if 和 then 区域. 这个区域中, shell 每执行一条命令就记录下 它的退出状态, 另一区域从 then 到 fi 或 else 之间, 最有一个区域是从 else 到 fi, 在 fi 之后又回到中立区.

shell 记录当前区域类型, 还必须记录在 WANT_THEN 区域中所执行的命令结果.

不同的区域处理方法不同. 特定的区域与程序在特定的状态联系在一起. process 通过 3 个函数来处理区域问题

  1. is_control_command 返回一个 boolean 变量告诉 process 这条命令是脚本语言的一部分还是一条可执行的命令
  2. do_control_command 处理关键字 if,then 和 fi. 每个关键字都是区域的界标.这个函数更新状态变量并执行必要的操作
  3. ok_to_execute 根据当前状态和条件命令的结果返回一个 boolean 值, 说明能搜执行当前命令.

smsh2.c: 修改后的代码

smsh2.c

/** smsh2.c - small - shell version 2
 ** small shell that supports command parsing
 ** and if..then..else..fi logic (by calling process())
 **/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include "smsh.h"

#define DFL_PROMPT ">"

int
main()
{
    char *cmdline, *prompt, **arglist;
    int result, process(char **);
    void setup();

    prompt = DFL_PROMPT;
    setup();

    while ((cmdline = next_cmd(prompt, stdin)) != NULL){
        if ((arglist = splitline(cmdline)) != NULL){
            result = process(arglist);
            freelist(arglist);
        }
        free(cmdline);
    }
    return 0;
}

void setup()
{
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
}

void fatal(char *s1, char *s2, int n)
{
    fprintf(stderr, "Error: %s, %s\n", s1, s2);
    exit(n);
}

process.c

/* process.c
 * command processing layer
 *
 * The process(char **arglist) function is called by the main loop
 * It sits in  front of the execute() function. This layer handles
 * two main classes of processing:
 * a) built-in functions (e.g. exit(), set, =, read,..)
 * b) control structures (e.g. if, while, for)
 */
#include <stdio.h>
#include "smsh.h"

int is_control_command(char *);
int do_control_command(char **);
int ok_to_execute();

int
process(char **args)
/*
 * purpose: process user command
 * returns: result of processing command
 * details: if a built-in then call appropriate function, if not
 *          execute()
 * errors: arise form subroutines, handled there
 */
{
    int rv = 0;

    if (args[0] == NULL)
        rv = 0;
    else if (is_control_command(args[0]))
        rv = do_control_command(args);
    else if (ok_to_execute())
        rv = execute(args);
    return rv;
}
controlflow.c
/* controlflow.c
 *
 * "if" processing is done with two state variables
 * if_state and if_result
 */
#include <stdio.h>
#include <string.h>
#include "smsh.h"

enum stats {NEUTRAL, WANT_THEN, THEN_BLOCK};
enum results {SUCCESS, FAIL};

static int if_state = NEUTRAL;
static int if_result = SUCCESS;
static int last_stat = 0;

int syn_err(char *);

int ok_to_execute()
/*
 * purpose: datermine the shell should execute a command
 * returns: 1 for yes, 0 for no
 * details: if in THEN_BLOCK and if_result was SUCCESS then yes
 *          if in THEN_BLOCK and if_result was FAIL then no
 *          if in WANT_THEN then syntax error (sh is different)
 */
{
    int rv = 1;
    if (if_state == WANT_THEN){
        syn_err("then expexted");
        rv = 0;
    }else if (if_state == THEN_BLOCK && if_result == SUCCESS){
        rv = 1;
    }else if (if_state == THEN_BLOCK && if_result == FAIL){
        rv = 0;
    }
    return rv;
}

int is_control_command(char *s)
/*
 * purpose: boolean to report if the command is a shell control command
 * returns: 0 or 1
 */
{
    return (strcmp(s, "if") == 0 || strcmp(s, "then") == 0 ||
            strcmp(s, "fi") == 0);
}

int do_control_command(char **args)
/*
 * purpose: Process "if", "then", "fi" - change state or detect error
 * returns: 0 if ok, -1 for syntax error
 */
{
    int process(char **);
    char *cmd = args[0];
    int rv = -1;
    if (strcmp(cmd, "if") == 0){
        // 查看代码是否在中立区
        if (if_state != NEUTRAL)
            rv = syn_err("if unexpected");
        else{
            last_stat = process(args + 1);
            if_result = (last_stat == 0 ? SUCCESS : FAIL);
            if_state = WANT_THEN;
            rv = 0;
        }
    }else if(strcmp(cmd, "then") == 0){
        if (if_state != WANT_THEN)
            rv = syn_err("then unexpected");
        else{
            if_state = THEN_BLOCK;
            rv = 0;
        }
    }else if (strcmp(cmd, "fi") == 0){
        if (if_state != THEN_BLOCK)
            rv = syn_err("fi unexpected");
        else{
            if_state = NEUTRAL;
            rv = 0;
        }
    }else{
        fatal("internal error processing:", cmd, 2);
    }
    return rv;
}

int syn_err(char *msg)
/* purpose: handles syntax errors in control structures
 * details: resets state to NEUTRAL
 * returns: -1 in interactive mode. Should call fatal in scripts
 */
{
    if_state = NEUTRAL;
    fprintf(stderr, "syntax error: %s\n", msg);
    return -1;
}

编译

cc -o smsh2 smsh2.c splitline.c execute.c process.c controlflow.c